/****************************************************
 ****************************************************
 ***                                              ***
 ***   Project:  YAVRS                            ***
 ***   (Yet Another Virtual Reality System)       ***
 ***   ________________________________________   ***
 ***                                              ***
 ***   File:     yavrsWavefrontObj.cpp            ***
 ***   Authors:  Steve Conner & Thomas Marrinan   ***
 ***   Date:     11/05/2011                       ***
 ***                                              ***
 ****************************************************
 ****************************************************/

#include <cmath>
#include "yavrsWavefrontObj.h"

using namespace std;
	

/*** ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ***/
yavrsWavefrontObj::yavrsWavefrontObj() {
	bounds[0] =  9.9e12;
	bounds[1] = -9.9e12;
	bounds[2] =  9.9e12;
	bounds[3] = -9.9e12;
	bounds[4] =  9.9e12;
	bounds[5] = -9.9e12;
}

/*** ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ***/
yavrsWavefrontObj::~yavrsWavefrontObj() {
	vertList.clear();
	normList.clear();
	objList.clear();
	matList.clear();
}

/*** ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ***/
void yavrsWavefrontObj::calculateNormals() {
	int i, j, k, l, m;
	
	for(i=0; i<objList.size(); i++){
		for(j=0; j<objList[i]->faceList.size(); j++){
			// flat shading
			if(objList[i]->shadeMode == 0){
				int vertIdx1 = objList[i]->faceList[j][0][0] - 1;
				int vertIdx2 = objList[i]->faceList[j][1][0] - 1;
				int vertIdx3 = objList[i]->faceList[j][2][0] - 1;
				
				double v1[3], v2[3];
				double* normal = new double[3];
				v1[0] = vertList[vertIdx2][0] - vertList[vertIdx1][0];
				v1[1] = vertList[vertIdx2][1] - vertList[vertIdx1][1];
				v1[2] = vertList[vertIdx2][2] - vertList[vertIdx1][2];
				v2[0] = vertList[vertIdx3][0] - vertList[vertIdx1][0];
				v2[1] = vertList[vertIdx3][1] - vertList[vertIdx1][1];
				v2[2] = vertList[vertIdx3][2] - vertList[vertIdx1][2];
				
				normal[0] = v1[1]*v2[2] - v1[2]*v2[1];
				normal[1] = v1[2]*v2[0] - v1[0]*v2[2];
				normal[2] = v1[0]*v2[1] - v1[1]*v2[0];
				double mag = sqrt(normal[0]*normal[0] + normal[1]*normal[1] + normal[2]*normal[2]);
				normal[0] /= mag;
				normal[1] /= mag;
				normal[2] /= mag;
				
				normList.push_back(normal);
				objList[i]->faceList[j][0][1] = normList.size();
				objList[i]->faceList[j][1][1] = normList.size();
				objList[i]->faceList[j][2][1] = normList.size();
			}
			// smooth shading
			else{
				for(k=0; k<3; k++){
					int v = objList[i]->faceList[j][k][0] - 1;
					
					double* sumNorm = new double[3];
					sumNorm[0] = 0.0;
					sumNorm[1] = 0.0;
					sumNorm[2] = 0.0;
					
					for(l=0; l<objList[i]->faceList.size(); l++){
						int vertIdx1 = objList[i]->faceList[l][0][0] - 1;
						int vertIdx2 = objList[i]->faceList[l][1][0] - 1;
						int vertIdx3 = objList[i]->faceList[l][2][0] - 1;
						
						if((v == vertIdx1 || v == vertIdx2 || v == vertIdx3) && vertIdx1 < vertList.size() && vertIdx2 < vertList.size() && vertIdx3 < vertList.size()){
							double v1[3], v2[3], normal[3];
							v1[0] = vertList[vertIdx2][0] - vertList[vertIdx1][0];
							v1[1] = vertList[vertIdx2][1] - vertList[vertIdx1][1];
							v1[2] = vertList[vertIdx2][2] - vertList[vertIdx1][2];
							v2[0] = vertList[vertIdx3][0] - vertList[vertIdx1][0];
							v2[1] = vertList[vertIdx3][1] - vertList[vertIdx1][1];
							v2[2] = vertList[vertIdx3][2] - vertList[vertIdx1][2];
							
							normal[0] = v1[1]*v2[2] - v1[2]*v2[1];
							normal[1] = v1[2]*v2[0] - v1[0]*v2[2];
							normal[2] = v1[0]*v2[1] - v1[1]*v2[0];
							double mag = sqrt(normal[0]*normal[0] + normal[1]*normal[1] + normal[2]*normal[2]);
							normal[0] /= mag;
							normal[1] /= mag;
							normal[2] /= mag;
							
							sumNorm[0] += normal[0];
							sumNorm[1] += normal[1];
							sumNorm[2] += normal[2];
						}
					}
					
					double magnitude = sqrt(sumNorm[0]*sumNorm[0] + sumNorm[1]*sumNorm[1] + sumNorm[2]*sumNorm[2]);
					
					sumNorm[0] /= magnitude;
					sumNorm[1] /= magnitude;
					sumNorm[2] /= magnitude;
					
					normList.push_back(sumNorm);
					objList[i]->faceList[j][k][1] = normList.size();
				}
			}
		}
	}
}

/*** ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ***/
void yavrsWavefrontObj::calculateBounds() {
	int i;
    bounds[0] =  9.9e12;
	bounds[1] = -9.9e12;
	bounds[2] =  9.9e12;
	bounds[3] = -9.9e12;
	bounds[4] =  9.9e12;
	bounds[5] = -9.9e12;
	for(i=0; i<vertList.size(); i++){
		if(vertList[i][0] < bounds[0]) bounds[0] = vertList[i][0];
		if(vertList[i][0] > bounds[1]) bounds[1] = vertList[i][0];
		if(vertList[i][1] < bounds[2]) bounds[2] = vertList[i][1];
		if(vertList[i][1] > bounds[3]) bounds[3] = vertList[i][1];
		if(vertList[i][2] < bounds[4]) bounds[4] = vertList[i][2];
		if(vertList[i][2] > bounds[5]) bounds[5] = vertList[i][2];
	}
}

/*** ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ***/
double* yavrsWavefrontObj::getBounds() {
	return bounds;
}

/*** ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ***/
void yavrsWavefrontObj::normalizeObject() {
    double xLength = bounds[1] - bounds[0];
    double yLength = bounds[3] - bounds[2];
    double zLength = bounds[5] - bounds[4];
    double xCenter = (bounds[0]+bounds[1]) / 2.0;
    double yCenter = (bounds[2]+bounds[3]) / 2.0;
    double zCenter = (bounds[4]+bounds[5]) / 2.0;
    
    double maxLength;
    if(xLength > yLength && xLength > zLength) maxLength = xLength;
    else if(yLength > zLength) maxLength = yLength;
    else maxLength = zLength;
    
    // Translate each point -(Center), then scale by 1.0/maxLength
    int i;
    for(i=0; i<vertList.size(); i++){
        vertList[i][0] = (vertList[i][0]-xCenter) * (1.0/maxLength);
        vertList[i][1] = (vertList[i][1]-yCenter) * (1.0/maxLength);
        vertList[i][2] = (vertList[i][2]-zCenter) * (1.0/maxLength);
    }
    calculateBounds();
}

void yavrsWavefrontObj::loadWaveFile(const char * filename) {
    // Read WAVE File
    FILE *fp = fopen(filename, "rb");

    if(!fp) {
         return;
    }

    char type[4];
    unsigned int size, chunkSize;
    short formatType, channels;
    unsigned int sampleRate, avgBytesPerSec;
    short bytesPerSample, bitsPerSample;
    unsigned int dataSize;
    
    fread(type, sizeof(char), 4, fp);
    if(type[0] != 'R' || type[1] != 'I' || type[2] != 'F' || type[3] != 'F'){
	printf("No RIFF\n");
	return;
    }
    fread(&size, 4, 1, fp);
    fread(type, sizeof(char), 4, fp);
    if(type[0] != 'W' || type[1] != 'A' || type[2] != 'V' || type[3] != 'E'){
	printf("Not a WAVE\n");
	return;
    }
    fread(type, sizeof(char), 4, fp);
    if(type[0] != 'f' || type[1] != 'm' || type[2] != 't' || type[3] != ' '){
	printf("Not fmt \n");
	return;
    }
    fread(&chunkSize, 4, 1, fp);
    fread(&formatType, sizeof(short), 1, fp);
    fread(&channels, sizeof(short), 1, fp);
    fread(&sampleRate, 4, 1, fp);
    fread(&avgBytesPerSec, 4, 1, fp);
    fread(&bytesPerSample, sizeof(short), 1, fp);
    fread(&bitsPerSample, sizeof(short), 1, fp);
    fread(type, sizeof(char), 4, fp);
    if(type[0] != 'd' || type[1] != 'a' || type[2] != 't' || type[3] != 'a'){
	printf("Missing data\n");
	return;
    }
    fread(&dataSize, 4, 1, fp);
    unsigned char *buf = new unsigned char[dataSize];
    fread(buf, 1, dataSize, fp);
    
    // OpenAL Sound Source and Listener
    ALuint frequency = sampleRate;
    ALenum format = 0;
    alGenBuffers(1, &buffer);
    alGenSources(1, &source);
    if(bitsPerSample == 8){
	if(channels == 1){
	    format = AL_FORMAT_MONO8;
	}
	else if(channels == 2){
	    format = AL_FORMAT_STEREO8;
	}
    }
    else if(bitsPerSample == 16){
	if(channels == 1){
	    format = AL_FORMAT_MONO16;
	}
	else if(channels == 2){
	    format = AL_FORMAT_STEREO16;
	}
    }
    alBufferData(buffer, format, buf, dataSize, frequency);
    ALfloat SourceVel[]   = {0.0, 0.0,  0.0};
    alSourcei(source, AL_BUFFER, buffer);
    alSourcef(source, AL_PITCH, 1.0);
    alSourcef(source, AL_GAIN, 1.0);
    alSourcefv(source, AL_POSITION, position);
    alSourcefv(source, AL_VELOCITY, SourceVel);
    alSourcei(source, AL_LOOPING, AL_FALSE);
    
    // Print Info
    printf("Chunk Size:  %d, Channels:  %d\n", chunkSize, channels);
    printf("Sample Rate: %d, Bytes/Sec: %d\n", sampleRate, avgBytesPerSec);
    printf("Bytes/Samp:  %d, Bits/Samp: %d\n", bytesPerSample, bitsPerSample);
    if(format == AL_FORMAT_MONO8)    printf("Format: Mono 8\n");
    if(format == AL_FORMAT_STEREO8)  printf("Format: Stereo 8\n");
    if(format == AL_FORMAT_MONO16)   printf("Format: Mono 16\n");
    if(format == AL_FORMAT_STEREO16) printf("Format: Stereo 16\n");
    
    // Clean Up
    /*fclose(fp);
    delete[] buf;
    alDeleteSources(1, &source);
    alDeleteBuffers(1, &buffer);
    alcMakeContextCurrent(NULL);
    alcDestroyContext(context);
    alcCloseDevice(device);*/
}

void yavrsWavefrontObj::playWaveFile()
{
	if(source){
		alSourcePlay(source);
	}
}

/*** ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ***/
void yavrsWavefrontObj::load(const char* fileName) {
	ifstream fileIn;
	string line;
	char mtlName[60];
	int i, j, k, l, m, pos;
	int curObj = -1;
	int curMat = -1;
	
	string fileDir = fileName;
	pos = fileDir.rfind("/");
	if(pos < 0){
		pos = fileDir.rfind("\\");
	}
	if(pos >= 0){
		fileDir = fileDir.substr(0, pos+1);
	}
	else{
		fileDir = "./";
	}
	
	fileIn.open(fileName);
	if(!fileIn.is_open()){
		printf("Error, cannot open OBJ file.\n");
		return;
	}
	
	// read OBJ line-by-line
	while(getline(fileIn, line)){
		// find MTL file name
		if(line.substr(0, 7) == "mtllib "){
			sscanf(line.c_str(), "mtllib %s", mtlName);
		}
		
		// find object material
		if(line.substr(0, 7) == "usemtl "){
			char objMtl[60];
			sscanf(line.c_str(), "usemtl %s", objMtl);
			object* newObj = new object();;
			strcpy(newObj->matName, objMtl);
			newObj->shadeMode = -1;
			objList.push_back(newObj);
			curObj++;
		}
		
		// find vertices
		if(line.substr(0, 2) == "v "){
			double* vertex = new double[3];
			sscanf(line.c_str(), "v %lf %lf %lf", &vertex[0], &vertex[1], &vertex[2]);
			vertList.push_back(vertex);
		}
		
		// find shade mode
		if(line.substr(0, 2) == "s "){
			char shade[10];
			sscanf(line.c_str(), "s %s", shade);
			if(strcmp(shade, "off") == 0) objList[curObj]->shadeMode = 0;
			else objList[curObj]->shadeMode = 1;
		}
		
		// find faces
		if(line.substr(0, 2) == "f "){
			if(objList[curObj]->shadeMode == -1) objList[curObj]->shadeMode = objList[curObj-1]->shadeMode;
			int i;
			int** face = new int*[3];
			for(i=0; i<3; i++){
				face[i] = new int[2];
			}
			sscanf(line.c_str(), "f %d %d %d", &face[0][0], &face[1][0], &face[2][0]);
			objList[curObj]->faceList.push_back(face);
		}
	}
	fileIn.close();
	
	calculateNormals();
	calculateBounds();
	
	
	string mtlFile = mtlName;
	fileIn.open((fileDir+mtlFile).c_str());
	if(!fileIn.is_open()){
		printf("Error, cannot open MTL file.\n");
		return;
	}
	
	// read MTL line-by-line
	while(getline(fileIn, line)){
		// find New Material
		if(line.substr(0, 7) == "newmtl "){
			char objMtl[60];
			sscanf(line.c_str(), "newmtl %s", objMtl);
			material* newMat = new material();
			strcpy(newMat->matName, objMtl);
			matList.push_back(newMat);
			curMat++;
		}
		
		// find Ka
		if(line.substr(0, 3) == "Ka "){
			sscanf(line.c_str(), "Ka %f %f %f", &matList[curMat]->Ka[0], &matList[curMat]->Ka[1], &matList[curMat]->Ka[2]);
		}
		
		// find Kd
		if(line.substr(0, 3) == "Kd "){
			sscanf(line.c_str(), "Kd %f %f %f", &matList[curMat]->Kd[0], &matList[curMat]->Kd[1], &matList[curMat]->Kd[2]);
		}
		
		// find Ks
		if(line.substr(0, 3) == "Ks "){
			sscanf(line.c_str(), "Ks %f %f %f", &matList[curMat]->Ks[0], &matList[curMat]->Ks[1], &matList[curMat]->Ks[2]);
		}
		
		// find Ns
		if(line.substr(0, 3) == "Ns "){
			sscanf(line.c_str(), "Ns %f", &matList[curMat]->Ns[0]);
			if(matList[curMat]->Ns[0] > 128.0) matList[curMat]->Ns[0] = 128.0;
		}
		
		// find d - alpha
		if(line.substr(0, 2) == "d "){
			float d;
			sscanf(line.c_str(), "d %f", &d);
			matList[curMat]->Ka[3] = matList[curMat]->Kd[3] = matList[curMat]->Ks[3] = d;
		}
		
		// find illum
		if(line.substr(0, 6) == "illum "){
			sscanf(line.c_str(), "illum %d", &matList[curMat]->illum);
		}
	}
	fileIn.close();
}
	
/*** ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ***/
void yavrsWavefrontObj::draw() {
	/*
	glPushMatrix();
	glTranslatef(position[0], position[1], position[2]);
	glScalef(scale[0], scale[1], scale[2]);
	GLUquadricObj * q = gluNewQuadric();
	gluSphere(q, radius, 20, 20);
	glPopMatrix();
	*/


	glPushMatrix();
	glTranslatef(position[0], position[1], position[2]);
	glRotatef(rotation[0], rotation[1], rotation[2], rotation[3]);
	glScalef(scale[0], scale[1], scale[2]);


	int i, j;
    // opaque faces
	for(i=0; i<objList.size(); i++){
		int matIdx;
		for(j=0; j<matList.size(); j++){
			if(strcmp(objList[i]->matName, matList[j]->matName) == 0) matIdx = j;
		}
		
        if(matList[matIdx]->Kd[3] == 1.0){
            glMaterialfv(GL_FRONT, GL_AMBIENT, matList[matIdx]->Ka);
            glMaterialfv(GL_FRONT, GL_DIFFUSE, matList[matIdx]->Kd);
            if(matList[matIdx]->illum == 2){
                glMaterialfv(GL_FRONT, GL_SPECULAR, matList[matIdx]->Ks);

                glMaterialfv(GL_FRONT, GL_SHININESS, matList[matIdx]->Ns);
            }
            else{
                float noSpecular[] = {0.0, 0.0, 0.0, 0.0};
                float noShininess[] = {0.0};
                glMaterialfv(GL_FRONT, GL_SPECULAR, noSpecular);
                glMaterialfv(GL_FRONT, GL_SHININESS, noShininess);
            }
            
            glBegin(GL_TRIANGLES);
            for(j=0; j<objList[i]->faceList.size(); j++){
                int vertIdx1 = objList[i]->faceList[j][0][0] - 1;
                int vertIdx2 = objList[i]->faceList[j][1][0] - 1;
                int vertIdx3 = objList[i]->faceList[j][2][0] - 1;
                int normIdx1 = objList[i]->faceList[j][0][1] - 1;
                int normIdx2 = objList[i]->faceList[j][1][1] - 1;
                int normIdx3 = objList[i]->faceList[j][2][1] - 1;
                    
                if(vertIdx1 >= 0 && vertIdx1 < vertList.size() && vertIdx2 >= 0 && vertIdx2 < vertList.size()
                   && vertIdx3 >= 0 && vertIdx3 < vertList.size()){
                    
                    glNormal3f(normList[normIdx1][0], normList[normIdx1][1], normList[normIdx1][2]);
                    glVertex3f(vertList[vertIdx1][0], vertList[vertIdx1][1], vertList[vertIdx1][2]);
                    glNormal3f(normList[normIdx2][0], normList[normIdx2][1], normList[normIdx2][2]);
                    glVertex3f(vertList[vertIdx2][0], vertList[vertIdx2][1], vertList[vertIdx2][2]);
                    glNormal3f(normList[normIdx3][0], normList[normIdx3][1], normList[normIdx3][2]);
                    glVertex3f(vertList[vertIdx3][0], vertList[vertIdx3][1], vertList[vertIdx3][2]);
                }
            }
            glEnd();
        }
	}
    // semi-transparent faces
    for(i=0; i<objList.size(); i++){
		int matIdx;
		for(j=0; j<matList.size(); j++){
			if(strcmp(objList[i]->matName, matList[j]->matName) == 0) matIdx = j;
		}
		
        if(matList[matIdx]->Kd[3] < 1.0){
            glMaterialfv(GL_FRONT, GL_AMBIENT, matList[matIdx]->Ka);
            glMaterialfv(GL_FRONT, GL_DIFFUSE, matList[matIdx]->Kd);
            if(matList[matIdx]->illum == 2){
                glMaterialfv(GL_FRONT, GL_SPECULAR, matList[matIdx]->Ks);
                
                glMaterialfv(GL_FRONT, GL_SHININESS, matList[matIdx]->Ns);
            }
            else{
                float noSpecular[] = {0.0, 0.0, 0.0, 0.0};
                float noShininess[] = {0.0};
                glMaterialfv(GL_FRONT, GL_SPECULAR, noSpecular);
                glMaterialfv(GL_FRONT, GL_SHININESS, noShininess);
            }
            
            glBegin(GL_TRIANGLES);
            for(j=0; j<objList[i]->faceList.size(); j++){
                int vertIdx1 = objList[i]->faceList[j][0][0] - 1;
                int vertIdx2 = objList[i]->faceList[j][1][0] - 1;
                int vertIdx3 = objList[i]->faceList[j][2][0] - 1;
                int normIdx1 = objList[i]->faceList[j][0][1] - 1;
                int normIdx2 = objList[i]->faceList[j][1][1] - 1;
                int normIdx3 = objList[i]->faceList[j][2][1] - 1;
                
                if(vertIdx1 >= 0 && vertIdx1 < vertList.size() && vertIdx2 >= 0 && vertIdx2 < vertList.size()
                   && vertIdx3 >= 0 && vertIdx3 < vertList.size()){
                    
                    glNormal3f(normList[normIdx1][0], normList[normIdx1][1], normList[normIdx1][2]);
                    glVertex3f(vertList[vertIdx1][0], vertList[vertIdx1][1], vertList[vertIdx1][2]);
                    glNormal3f(normList[normIdx2][0], normList[normIdx2][1], normList[normIdx2][2]);
                    glVertex3f(vertList[vertIdx2][0], vertList[vertIdx2][1], vertList[vertIdx2][2]);
                    glNormal3f(normList[normIdx3][0], normList[normIdx3][1], normList[normIdx3][2]);
                    glVertex3f(vertList[vertIdx3][0], vertList[vertIdx3][1], vertList[vertIdx3][2]);
                }
            }
            glEnd();
        }
	}
	glPopMatrix();
}

void yavrsWavefrontObj::setPosition(float x, float y, float z) {
	position[0] = x;
	position[1] = y;
	position[2] = z;
}

void yavrsWavefrontObj::setRotation(float angle, float x, float y, float z) {
	rotation[0] = angle;
	rotation[1] = x;
	rotation[2] = y;
	rotation[3] = z;
}

void yavrsWavefrontObj::setScale(float x, float y, float z) {
	scale[0] = x;
	scale[1] = y;
	scale[2] = z;
}

double yavrsWavefrontObj::rayCollision(double *p1, double *p2) {
	double maxScale;
    if(scale[0] > scale[1] && scale[0] > scale[2]) maxScale = scale[0];
    else if(scale[1] > scale[2]) maxScale = scale[1];
    else maxScale = scale[2];
    
    double deltaX = p2[0] - p1[0];
    double deltaY = p2[1] - p1[1];
    double deltaZ = p2[2] - p1[2];
    
    double x0_a = p1[0]-position[0];
    double y0_b = p1[1]-position[1];
    double z0_c = p1[2]-position[2];
    double rad = 0.5 * maxScale;
    
    double quad_A = (deltaX*deltaX) + (deltaY*deltaY) + (deltaZ*deltaZ);
    double quad_B = 2*(deltaX*(x0_a) + deltaY*(y0_b) + deltaZ*(z0_c));
    double quad_C = (x0_a*x0_a) + (y0_b*y0_b) +(z0_c*z0_c) - (rad*rad);
    
    double t[2];
    double root = (quad_B*quad_B) - (4*quad_A*quad_C);
    double closestPt = 9.9e12;
    if(quad_A != 0 && root >= 0){
        double D = sqrt(root);
        t[0] = ( -quad_B + D)/(2*quad_A);
        t[1] = ( -quad_B - D)/(2*quad_A);
        // t < 0  ==> intersection behind wii-mote
        if(t[0] >= 0.0 && t[0] < closestPt){
            closestPt = t[0];
        }
        if(t[1] >= 0.0 && t[1] < closestPt){
            closestPt = t[1];
        }
    }
    
    if(closestPt >= 9.9e11) closestPt = -1.0;
    
    return closestPt;
}




